if BotAI == nil then BotAI = class({}) end


BotCmdList = {
    REFRESH_ABILITIES_SHOP="REFRESH_ABILITIES_SHOP",
    PICKUP_RUNE="PICKUP_RUNE",
    PICKUP_GOLD="PICKUP_GOLD",
    MOVE_TO_TP="MOVE_TO_TP",
    BUY_ABILITIES="BUY_ABILITIES",
    ATTACK_CREEP="ATTACK_CREEP",
    PICKUP_ITEM="PICKUP_ITEM",
    ATTACK_HERO="ATTACK_HERO",
    MOVE_TO_BOSS_TP="MOVE_TO_BOSS_TP",
    ATTACK_BOSS="ATTACK_BOSS",
    PICK_BOSS_ITEM="PICK_BOSS_ITEM",
    ATTACK_COURIER="ATTACK_COURIER"
}

Scores={
    ATTACK_BOSS=10,
    REFRESH_ABILITIES_SHOP=10,
    PICKUP_RUNE=4,
    PICKUP_GOLD=5,
    BUY_ABILITIES=10,
    ATTACK_CREEP=7,
    MOVE_TO_TP=3,
    MOVE_TO_BOSS_TP=10,
    PICKUP_ITEM=9,
    ATTACK_HERO=8,
    ATTACK_COURIER=10,
    PICK_BOSS_ITEM=10
}

UNIT_FILTER = DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES + DOTA_UNIT_TARGET_FLAG_NO_INVIS
BotAI.BOT_AI_DEBUG=false
DontCastAbility = {
    [1] = AbilitiesNames.ability_tp,
}

function BotAI:AddAI(hero)
    hero:SetContextThink("OnBotThink", function() return BotAI:OnBotThink(hero) end, 1)
end

function BotAI:RemoveAI(hero)
    hero:SetContextThink("OnBotThink", function()
    end ,0)
end

function BotAI:OnBotThink(hero)
    if IsClient() or GameRules.G.isGameOver then return nil end

    local highestScoreCommand = 1
    local highestScore = 0
    local highestData = nil

    for i, v in pairs(BotCmdList) do
        local score, cmdData = BotAI:EvaluateCommand(hero, v)
        --print("[BotAI:OnBotThink] EvaluateCommand   ", v,"    score    ",score)
        if(score == nil) then
            score = 0
        end
        if(score > highestScore or (score == highestScore and RollPercentage(50))) then
            highestScore = score
            highestScoreCommand = i
            highestData = cmdData
        end
    end

    if highestScore~=0 then
        if BotAI.BOT_AI_DEBUG==true then
            print("[BotAI:OnBotThink] ExecuteCommand   ", BotCmdList[highestScoreCommand],"    highestScore    ",highestScore)
        end
      return BotAI:ExecuteCommand(hero, BotCmdList[highestScoreCommand], highestData)
    end

    --print("[BotAI:OnBotThink] nothing to do")
    return 1
end

function BotAI:EvaluateCommand(hero, cmdName)
    local score = 0

    if (cmdName == BotCmdList.REFRESH_ABILITIES_SHOP) then
        if table.count(AbilitiesShop:GetAbilitiesList(hero:GetPlayerID()))==GameConfig.abilitiesShopRandomCount then
            return 0,nil
        end
        if hero.randomAbilityType==nil then
            hero.randomAbilityType=AbilitiesShop.AbilityType.Basic
        end
        local col=GameConfig.abilitiesSlotCount/3
        local abilityTypes={}
        for i=0,GameConfig.abilitiesSlotCount-1 do
            if hero:GetAbilityByIndex(i):GetAbilityName()==AbilitiesNames.ability_empty then
                local row= math.floor(i/col)
                if row==0 then
                    abilityTypes[1]=AbilitiesShop.AbilityType.Basic
                elseif row==1 then
                    abilityTypes[2]=AbilitiesShop.AbilityType.Attr
                elseif row==2 then
                    abilityTypes[3]=AbilitiesShop.AbilityType.Ult
                end
            end
        end
        if table.count(abilityTypes)==0 then
            abilityTypes[1]=AbilitiesShop.AbilityType.Pt
        end
        local abilityType
        if table.contains(abilityTypes,hero.randomAbilityType) then
            abilityType=hero.randomAbilityType
        else
            local value,index= table.random(abilityTypes)
            abilityType=value
        end
        local abilityCostGold= AbilitiesShop:GetPrice(abilityType)
        if Resources:GetCustomGold(hero)>=abilityCostGold then
            return Scores.REFRESH_ABILITIES_SHOP,{playerId=hero:GetPlayerID(),
                                                  abilityType=abilityType}
        end
    elseif (cmdName==BotCmdList.BUY_ABILITIES) then
        local abilityEmpty=  hero:FindAbilityByName(AbilitiesNames.ability_empty)
        if abilityEmpty~=nil then
            local abilities=AbilitiesShop:GetAbilitiesList(hero:GetPlayerID())
            if table.count(abilities)==GameConfig.abilitiesShopRandomCount then
                local ability,index= table.random(abilities)
                local col=GameConfig.abilitiesSlotCount/3
                local startFindIndex,endFindIndex
                --table.print(ability)
                if ability["abilityType"]==AbilitiesShop.AbilityType.Attr then
                    startFindIndex=col
                    endFindIndex=col*2
                elseif ability["abilityType"]==AbilitiesShop.AbilityType.Basic then
                    startFindIndex=0
                    endFindIndex=col
                elseif ability["abilityType"]==AbilitiesShop.AbilityType.Ult then
                    startFindIndex=col*2
                    endFindIndex=col*3
                end
                for i=startFindIndex,endFindIndex do
                    if hero:GetAbilityByIndex(i):GetAbilityName()==AbilitiesNames.ability_empty then
                        return Scores.BUY_ABILITIES, {playerId=hero:GetPlayerID(),
                                                      abilitiesShopIndex=tonumber(index),
                                                      abilitiesHeroIndex=tonumber(i)
                        }
                    end
                end
            end
        end
    elseif (cmdName==BotCmdList.PICKUP_RUNE) then
        if(hero:IsChanneling()) then
            return 0, nil
        end
        if hero.mapPosIndex==-2 or (Map.centerPosList[hero.mapPosIndex]==nil) then
            return 0,nil
        end
        local runPos= Map.runePosList[hero.mapPosIndex]
        local rune = Entities:FindByClassnameNearest( "dota_item_rune",runPos,200)
        if rune~=nil then
            return Scores.PICKUP_RUNE, rune
        end
    elseif(cmdName==BotCmdList.PICKUP_GOLD) then
        if(hero:IsChanneling()) then
            return 0, nil
        end
        if hero.mapPosIndex==-2 or (Map.centerPosList[hero.mapPosIndex]==nil) then
            return 0,nil
        end
        local goldPos= Map.goldPosList[hero.mapPosIndex]
        local gold= Entities:FindByModelWithin(nil, ModelNames.gold,goldPos,200)
        if gold~=nil then
            return Scores.PICKUP_GOLD, gold
        end
    elseif(cmdName==BotCmdList.LEARN_TALENT) then
       local talentTable= CustomNetTables:GetTableValue(NetTableNames.talent_manager,tostring(hero:GetPlayerID()))
        if talentTable~=nil then
            for _,value in pairs(talentTable) do
                if value~=nil then
                    for _2,value2 in pairs(value) do
                        if value2.canLearn==true or value2.canLearn==1 then
                            return Scores.LEARN_TALENT,{
                                playerId=hero:GetPlayerID(),
                                row=_,
                                col=_2
                            }
                        end
                    end
                end
            end
        end
    elseif(cmdName==BotCmdList.ATTACK_BOSS) then
        if BotAI:CheckCanAction(hero)==false then
            return 0, nil
        end
        if hero.mapPosIndex==-2 then
            local creeps = FindUnitsInRadius(DOTA_TEAM_NEUTRALS,hero:GetAbsOrigin(), nil, 1000, DOTA_UNIT_TARGET_TEAM_FRIENDLY, DOTA_UNIT_TARGET_ALL, DOTA_UNIT_TARGET_FLAG_NONE, FIND_ANY_ORDER, false )
            if #creeps>0 then
                local creep
                for index = 1, #creeps do
                    if creeps[index]:IsAlive() and creeps[index]:IsInvulnerable() == false and creeps[index]:IsAttackImmune() == false and creeps[index]:IsInvisible()==false then
                        creep=creeps[index]
                    end
                end
                return Scores.ATTACK_CREEP, creep
            end
        end
    elseif(cmdName==BotCmdList.ATTACK_COURIER) then
        if BotAI:CheckCanAction(hero)==false then
            return 0, nil
        end
        if hero.mapPosIndex==-2 or (Map.centerPosList[hero.mapPosIndex]==nil) then
            return 0, nil
        end
        local couriers = FindUnitsInRadius(hero:GetTeamNumber(),Map.centerPosList[hero.mapPosIndex], nil, 1800, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_COURIER, DOTA_UNIT_TARGET_FLAG_NONE, 0, true)
        if #couriers>0 then
            local enemy
            local minDistance=-1
            for index = 1, #couriers do
                if couriers[index]:IsAlive() and couriers[index]:IsInvulnerable() == false  and couriers[index]:IsInvisible()==false then
                    local distance=  CalcDistanceBetweenEntityOBB(enemy,couriers[index])
                    if enemy==nil then
                        enemy=couriers[index]
                        minDistance=distance>=0 and distance or 0
                    else
                        if distance<minDistance then
                            minDistance=distance
                            enemy=couriers[index]
                        end
                    end
                end
            end
            return Scores.ATTACK_COURIER, enemy
        end
    elseif(cmdName==BotCmdList.ATTACK_HERO) then
        if BotAI:CheckCanAction(hero)==false then
            return 0, nil
        end
        if hero.mapPosIndex==-2 or (Map.centerPosList[hero.mapPosIndex]==nil) then
            return 0, nil
        end
        local heroes = FindUnitsInRadius(hero:GetTeamNumber(),Map.centerPosList[hero.mapPosIndex], nil, 1800, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO+DOTA_UNIT_TARGET_COURIER, UNIT_FILTER, 0, true)
        if #heroes>0 then
            local heroEnemy
            local minDistance=-1
            for index = 1, #heroes do
                if heroes[index]:IsAlive() and heroes[index]:IsInvulnerable() == false and heroes[index]:IsAttackImmune() == false and heroes[index]:IsInvisible()==false then
                    local distance=  CalcDistanceBetweenEntityOBB(heroEnemy,heroes[index])
                    if heroEnemy==nil then
                        heroEnemy=heroes[index]
                        minDistance=distance>=0 and distance or 0
                    else
                        if distance<minDistance then
                            minDistance=distance
                            heroEnemy=heroes[index]
                        end
                    end
                end
            end
            return Scores.ATTACK_HERO, heroEnemy
        end
    elseif(cmdName==BotCmdList.ATTACK_CREEP) then
        if BotAI:CheckCanAction(hero)==false then
            return 0, nil
        end
        if hero.mapPosIndex==-2 or (Map.centerPosList[hero.mapPosIndex]==nil) then
            return 0, nil
        end
        local creeps = FindUnitsInRadius(DOTA_TEAM_NEUTRALS,Map.centerPosList[hero.mapPosIndex], nil, 1800, DOTA_UNIT_TARGET_TEAM_FRIENDLY, DOTA_UNIT_TARGET_ALL, DOTA_UNIT_TARGET_FLAG_NONE, FIND_ANY_ORDER, false )
        if #creeps>0 then
            local creep
            local minDistance=-1
            for index = 1, #creeps do
                if creeps[index]:IsAlive() and creeps[index]:IsInvulnerable() == false and creeps[index]:IsAttackImmune() == false and creeps[index]:IsInvisible()==false then
                    local distance=  CalcDistanceBetweenEntityOBB(hero,creeps[index])
                    if creep==nil then
                        creep=creeps[index]
                        minDistance=distance>=0 and distance or 0
                    else
                        if distance<minDistance then
                            minDistance=distance
                            creep=creeps[index]
                        end
                    end
                end
            end
            return Scores.ATTACK_CREEP, creep
        end
    elseif(cmdName==BotCmdList.PICK_BOSS_ITEM) then
        if BotAI:CheckCanAction(hero)==false then
            return 0, nil
        end
        if hero.mapPosIndex==-2 then
            local item= Entities:FindByClassnameNearest("dota_item_drop",hero:GetAbsOrigin(),1000)
            if item~=nil then
                if BotAI:HasRoomForPickupItem(hero)==true then
                    return Scores.PICK_BOSS_ITEM, item
                end
            end
        end
    elseif(cmdName==BotCmdList.PICKUP_ITEM) then
        if BotAI:CheckCanAction(hero)==false then
            return 0, nil
        end
        if hero.mapPosIndex==-2 or (Map.centerPosList[hero.mapPosIndex]==nil) then
            return 0, nil
        end
        local mapPos= Map.centerPosList[hero.mapPosIndex]
        local item= Entities:FindByClassnameNearest("dota_item_drop",mapPos,1800)
        if item~=nil then
            if BotAI:HasRoomForPickupItem(hero)==true then
                return Scores.PICKUP_ITEM, item
            end
        end
    elseif(cmdName==BotCmdList.MOVE_TO_TP) then
        if BotAI:CheckCanAction(hero)==false then
            return 0, nil
        end
        if hero.mapPosIndex==-2 then
            return 0, nil
        end
        if hero.mapPosIndex~=nil and hero.mapPosIndex~=-2 then
            if Map.centerPosList[hero.mapPosIndex]==nil then
                return 10,Map.tpPosList[hero.mapPosIndex]
            end
        end
        if BotAI:HasEnemyNearby(hero,Map.baseMapRang)==false then
            return Scores.MOVE_TO_TP,Map.tpPosList[hero.mapPosIndex]
        end
    elseif (cmdName==BotCmdList.MOVE_TO_BOSS_TP) then
        if BotAI:CheckCanAction(hero)==false then
            return 0, nil
        end
        --print(Map.bossTp,Map.bossTp.posIndex,hero.mapPosIndex)
        if Map.bossTp~=nil and Map.bossTp.mapPosIndex~=nil and Map.bossTp.mapPosIndex==hero.mapPosIndex then
            return Scores.MOVE_TO_BOSS_TP,Map.bossTp:GetAbsOrigin()
        end
    end
    return score,nil
end

function BotAI:GetCanCastAbility(hero)
    local canCastAbilities = {}
    for i = 0, GameConfig.abilitiesSlotCount-1 do
        local ability = hero:GetAbilityByIndex(i)
        local canCast = true

        if(ability == nil or ability:GetLevel() <= 0) then
            canCast = false
        elseif(ability:IsHidden() or ability:IsPassive() or ability:IsActivated() == false) then
            canCast = false
        elseif(string.find(ability:GetAbilityName(), "_bonus") ~= nil) then
            canCast = false
        elseif(ability:IsFullyCastable() == false or ability:IsCooldownReady() == false) then
            canCast = false
        elseif(BitContains(ability:GetBehavior(), DOTA_ABILITY_BEHAVIOR_AUTOCAST)) then
            if ability:GetAutoCastState() == false then
                ability:ToggleAutoCast()
            end
            canCast = false
        end
        if(canCast and ability:IsToggle() and ability:GetToggleState() == true) then
            canCast = false
        end
        if canCast and table.contains(DontCastAbility, ability:GetAbilityName()) == false then
            if BotAI.BOT_AI_DEBUG==true then
                print("canCastAbility..",ability:GetAbilityName())
            end
            table.insert(canCastAbilities, ability)
        end
    end

    --table.print(canCastAbilities)
    local selectedAbility = nil
    local ultimateAbility = nil
    local toggleAbility = nil



    if(#canCastAbilities > 0) then
        selectedAbility = canCastAbilities[RandomInt(1, #canCastAbilities)]
        for _, v in pairs(canCastAbilities) do
            if(v:GetAbilityType() == ABILITY_TYPE_ULTIMATE) then
                ultimateAbility = v
            elseif(v:IsToggle() and v:GetToggleState() == false ) then
                toggleAbility = v
            end
        end
    end

    if(toggleAbility ~= nil and BotAI:HasEnemyNearby(hero)) then
        return {ability = toggleAbility, type = "toggle", target = nil}
    end

    if(ultimateAbility ~= nil and RollPercentage(50)) then
        selectedAbility = ultimateAbility
    end

    if(selectedAbility ~= nil) then
        local spellData = BotAI:GetSpellData(selectedAbility)

        if(spellData == nil) then
            if BotAI.BOT_AI_DEBUG==true then
                print("[GetCanCastAbility]  spellData..nil")
            end
            return nil
        end
        --table.print(spellData)
        return spellData
    end
    return nil
end

function BotAI:GetCanUseItem(hero)
    local canCastItems = {}
    for slotIndex = 0, 5 do
        local item = hero:GetItemInSlot(slotIndex)
        if(item ~= nil) then
            local itemName = item:GetName()
            local canCast = true


            if(item:IsMuted() or item:IsPassive() or item:IsToggle()) then
                canCast = false
            elseif(item:RequiresCharges() and item:GetCurrentCharges() <= 0) then
                canCast = false
            elseif(item:IsFullyCastable() == false or item:IsCooldownReady() == false) then
                canCast = false
            elseif(itemName == "item_power_treads" or itemName == "item_bloodstone" or itemName == "item_moon_shard" or itemName == "item_bfury" or itemName == "item_sphere") then
                canCast = false
            elseif(itemName == "item_refresher" and (hero:GetName() == "npc_dota_hero_arc_warden" or hero:GetName() == "npc_dota_hero_abaddon")) then
                canCast = false
            elseif((itemName == "item_mekansm" or itemName == "item_guardian_greaves") and hero:GetHealth() > hero:GetMaxHealth() * 0.75) then
                canCast = false
            elseif(itemName == "item_mekansm" and hero:GetHealth() == hero:GetMaxHealth()) then
                canCast = false
            elseif((itemName == "item_blade_mail" or itemName == "item_satanic" or itemName == "item_cyclone") and hero:GetHealth() > hero:GetMaxHealth() * 0.65) then
                canCast = false
            end
            if canCast then
                table.insert(canCastItems, item)
            end
        end
    end

    local selectedItem = nil

    if(#canCastItems > 0) then
        selectedItem = canCastItems[RandomInt(1, #canCastItems)]
    end

    if(selectedItem ~= nil) then
        local itemName = selectedItem:GetName()

        if(hero:GetHealth() < hero:GetMaxHealth() * 0.9) then
            if(itemName == "item_black_king_bar") then
                return {ability = selectedItem, type = "no_target", target = nil}
            end
        end

        if(hero:GetHealth() < hero:GetMaxHealth() * 0.65) then
            if(itemName == "item_satanic" or itemName == "item_mekansm" or itemName == "item_guardian_greaves") then
                return {ability = selectedItem, type = "no_target", target = nil}
            end

            if(itemName == "item_cyclone" or itemName == "item_urn_of_shadows" or itemName == "item_spirit_vessel") then
                return {ability = selectedItem, type = "unit_target", target = hero}
            end
        end

        if(itemName == "item_necronomicon_3" or itemName == "item_necronomicon_2" or itemName == "item_necronomicon" or itemName == "item_manta") then
            return {ability = selectedItem, type = "no_target", target = nil}
        end

        local spellData = BotAI:GetSpellData(selectedItem)
        if(spellData == nil) then
            return nil
        end
        return spellData
    end
    return nil
end

function BotAI:ExecuteCommand(hero, cmdName, cmdData)
    if BotAI.BOT_AI_DEBUG==true then
        print("[BotAI:ExecuteCommand]   cmdName=",cmdName," ,cmdData=",cmdData)
    end
    local playerInfo=Player.playerList[hero:GetPlayerID()]
    if(playerInfo == nil) then
        return 2
    end

   if(cmdName == BotCmdList.ATTACK_CREEP) then
        --print(CalcDistanceBetweenEntityOBB(hero,cmdData))
       if CalcDistanceBetweenEntityOBB(hero,cmdData)<=900 then
           local spellData =BotAI:GetCanCastAbility(hero)
           if spellData~=nil then
               return BotAI:CastSpell(spellData)
           end
           spellData= BotAI:GetCanUseItem(hero)
           if spellData~=nil then
               return BotAI:CastSpell(spellData)
           end
       end
       hero:MoveToTargetToAttack(cmdData)
       return hero:GetAttackSpeed()+2
   elseif(cmdName==BotCmdList.ATTACK_COURIER) then
       hero:MoveToTargetToAttack(cmdData)
       return hero:GetAttackSpeed()+2
   elseif(cmdName==BotCmdList.ATTACK_BOSS) then
       if CalcDistanceBetweenEntityOBB(hero,cmdData)<=900 then
           local spellData =BotAI:GetCanCastAbility(hero)
           if spellData~=nil then
               return BotAI:CastSpell(spellData)
           end
           spellData= BotAI:GetCanUseItem(hero)
           if spellData~=nil then
               return BotAI:CastSpell(spellData)
           end
       end
       hero:MoveToTargetToAttack(cmdData)
       return hero:GetAttackSpeed()+2
   elseif(cmdName == BotCmdList.ATTACK_HERO) then
       if CalcDistanceBetweenEntityOBB(hero,cmdData)<=900 then
           local spellData =BotAI:GetCanCastAbility(hero)
           if spellData~=nil then
               return BotAI:CastSpell(spellData)
           end
           spellData= BotAI:GetCanUseItem(hero)
           if spellData~=nil then
               return BotAI:CastSpell(spellData)
           end
       end
       hero:MoveToTargetToAttack(cmdData)
       return hero:GetAttackSpeed()+2
    elseif(cmdName == BotCmdList.REFRESH_ABILITIES_SHOP) then
        GameMode:OnRefreshAbility(cmdData)
       if hero.randomAbilityType==nil then
           hero.randomAbilityType=AbilitiesShop.AbilityType.Basic
       elseif hero.randomAbilityType==AbilitiesShop.AbilityType.Basic then
           hero.randomAbilityType=AbilitiesShop.AbilityType.Attr
       elseif hero.randomAbilityType==AbilitiesShop.AbilityType.Attr then
           hero.randomAbilityType=AbilitiesShop.AbilityType.Ult
       elseif hero.randomAbilityType==AbilitiesShop.AbilityType.Ult then
           hero.randomAbilityType=AbilitiesShop.AbilityType.Basic
       end
        return 2
    elseif(cmdName == BotCmdList.BUY_ABILITIES) then
       --table.print(cmdData)
        GameMode:OnBuyAbility(cmdData)
        return 0.5
    elseif (cmdName==BotCmdList.MOVE_TO_BOSS_TP) then
       hero:MoveToPosition(cmdData)
       return 3
    elseif(cmdName == BotCmdList.MOVE_TO_TP) then
        hero:MoveToPosition(cmdData)
        return 3
    elseif(cmdName == BotCmdList.PICKUP_RUNE) then
        hero:PickupRune(cmdData)
    elseif(cmdName == BotCmdList.PICKUP_GOLD) then
        hero:PickupDroppedItem(cmdData)
   elseif(cmdName==BotCmdList.LEARN_TALENT) then
        --Talents:OnLearnTalent(cmdData)
   elseif(cmdName==BotCmdList.PICKUP_ITEM) or (cmdName==BotCmdList.PICK_BOSS_ITEM) then
        hero:PickupDroppedItem(cmdData)
   elseif (cmdName==BotCmdList.USE_ITEM) then
       local loopTime = BotAI:CastSpell(cmdData)
       return loopTime
    else
        print("no command name match")
    end
    return 2
end


function BotAI:CheckCanAction(hero)
    if(hero:IsChanneling() or hero:IsStunned() or hero:IsAttackImmune()) then
        return false
    end
    return true
end

function BotAI:HasRoomForPickupItem(hero)
    local itemCount = 0
    for slotIndex = 0, 5 do
        local item = hero:GetItemInSlot(slotIndex)
        if item ~= nil then
            itemCount = itemCount + 1
        end
    end
    return itemCount < 6
end



function BotAI:HasEnemyNearby(hero, range)
    if(range == nil) then
        range = 850
    end
    local units = FindUnitsInRadius(hero:GetTeamNumber(), hero:GetAbsOrigin(),
            hero, range, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO+DOTA_UNIT_TARGET_BASIC+DOTA_UNIT_TARGET_CREEP, UNIT_FILTER, 0, true)

    return #units > 0
end





function BotAI:HasTargetTrueSight(hero, target)
    if(target:HasModifier("modifier_truesight")) then
        local modifiers = target:FindAllModifiersByName("modifier_truesight")
        for i, v in pairs(modifiers) do
            local caster = v:GetCaster()
            if(caster ~= nil and caster:IsNull() == false) then
                if(caster:GetTeamNumber() == hero:GetTeamNumber()) then
                    return true
                end
            end
        end
    end
    return false
end

function BotAI:GetSpellData(hSpell)
    if hSpell == nil or hSpell:IsActivated() == false then
        return nil
    end



    local nBehavior = hSpell:GetBehavior()
    local nTargetTeam = hSpell:GetAbilityTargetTeam()
    local nTargetType = hSpell:GetAbilityTargetType()
    local nTargetFlags = hSpell:GetAbilityTargetFlags()
    local abilityName = hSpell:GetName()


    --table.print(hSpell: GetAbilityKeyValues())
    local keyValues=hSpell: GetAbilityKeyValues()
    if BotAI.BOT_AI_DEBUG==true then
        print("[GetSpellData] abilityName=",abilityName)
        print("AbilityUnitTargetTeam=",keyValues["AbilityUnitTargetTeam"])
        print("AbilityBehavior=",keyValues["AbilityBehavior"])
        print("AbilityUnitTargetFlags=",keyValues["AbilityUnitTargetFlags"])
        print("AbilityUnitTargetType=",keyValues["AbilityUnitTargetType"])
    end



    if BitContains(nTargetType, DOTA_UNIT_TARGET_TREE) then
        local treeTarget = BotAI:FindTreeTarget(hSpell)
        if treeTarget ~= nil then
            return {ability = hSpell, type = "tree_target", target = treeTarget}
        end
    elseif BitContains(nTargetTeam, DOTA_UNIT_TARGET_TEAM_ENEMY) then
        if BitContains(nBehavior, DOTA_ABILITY_BEHAVIOR_NO_TARGET) then
            if BotAI:IsNoTargetSpellCastValid(hSpell, DOTA_UNIT_TARGET_TEAM_ENEMY) then
                return {ability = hSpell, type = "no_target", target = nil}
            end
        elseif BitContains(nBehavior, DOTA_ABILITY_BEHAVIOR_DIRECTIONAL) then
            local vTargetLoc = BotAI:GetBestDirectionalPointTarget(hSpell, DOTA_UNIT_TARGET_TEAM_ENEMY)
            if vTargetLoc ~= nil then
                return {ability = hSpell, type = "point_target", target = vTargetLoc}
            end
        elseif BitContains(nBehavior, DOTA_ABILITY_BEHAVIOR_POINT) then
            local vTargetLoc = BotAI:GetBestAOEPointTarget(hSpell, DOTA_UNIT_TARGET_TEAM_ENEMY)
            if vTargetLoc ~= nil then
                return {ability = hSpell, type = "point_target", target = vTargetLoc}
            end
        elseif BitContains(nBehavior, DOTA_ABILITY_BEHAVIOR_UNIT_TARGET) then
            if BitContains(nBehavior, DOTA_ABILITY_BEHAVIOR_AOE) then
                local hTarget = BotAI:GetBestHeroTargetInRange(hSpell)
                if hTarget ~= nil and hTarget:IsNull() == false then
                    return {ability = hSpell, type = "unit_target", target = hTarget}
                end
            else
                local hTarget
                if BitContains(nTargetType, DOTA_UNIT_TARGET_BASIC)  then
                    hTarget = BotAI:GetBestCreepTarget(hSpell)
                end
                if BitContains(nTargetType, DOTA_UNIT_TARGET_HERO) then
                    if hTarget==nil then
                        hTarget= BotAI:GetBestHeroTargetInRange(hSpell)
                    end
                end
                if hTarget ~= nil and hTarget:IsNull() == false and hTarget:IsAlive() then
                    return {ability = hSpell, type = "unit_target", target = hTarget}
                end
            end
        end
    elseif BitContains(nTargetTeam, DOTA_UNIT_TARGET_TEAM_CUSTOM) then
        if BitContains(nTargetFlags, DOTA_UNIT_TARGET_FLAG_NOT_CREEP_HERO) then
            local hTarget = BotAI:GetBestCreepTarget(hSpell)
            if hTarget ~= nil and hTarget:IsNull() == false and hTarget:IsAlive() then
                return {ability = hSpell, type = "unit_target", target = hTarget}
            end
        else
            local hTarget = BotAI:GetBestHeroTargetInRange(hSpell)
            if hTarget ~= nil and hTarget:IsNull() == false and hTarget:IsAlive() then
                return {ability = hSpell, type = "unit_target", target = hTarget}
            end
        end
    elseif BitContains(nTargetTeam, DOTA_UNIT_TARGET_TEAM_FRIENDLY) then
        if(BotAI:HasEnemyNearby(hSpell:GetCaster(), 1500) == false) then
            return nil
        end
        if BitContains(nBehavior, DOTA_ABILITY_BEHAVIOR_NO_TARGET) then
            if(abilityName == "weaver_time_lapse") then
                if(hSpell:GetCaster():GetHealth() < hSpell:GetCaster():GetMaxHealth() * 0.75) then
                    return {ability = hSpell, type = "no_target", target = nil}
                end
                return nil
            end
            if BotAI:IsNoTargetSpellCastValid(hSpell, DOTA_UNIT_TARGET_TEAM_FRIENDLY) then
                return {ability = hSpell, type = "no_target", target = nil}
            end
        elseif BitContains(nBehavior, DOTA_ABILITY_BEHAVIOR_POINT) then
            local vTargetLoc = BotAI:GetBestAOEPointTarget(hSpell, DOTA_UNIT_TARGET_TEAM_FRIENDLY)
            if vTargetLoc ~= nil then
                return {ability = hSpell, type = "point_target", target = vTargetLoc}
            end
        else
            if hSpell:GetCaster()~=nil and hSpell:GetCaster():IsAlive() then
                return {ability = hSpell, type = "unit_target", target = hSpell:GetCaster()}
            end
        end
    else
        if BitContains(nBehavior, DOTA_ABILITY_BEHAVIOR_NO_TARGET) then
            if BotAI:IsNoTargetSpellCastValid(hSpell, DOTA_UNIT_TARGET_TEAM_ENEMY) then
                return {ability = hSpell, type = "no_target", target = nil}
            end
        elseif BitContains(nBehavior, DOTA_ABILITY_BEHAVIOR_POINT) then
            local vTargetLoc = BotAI:GetBestAOEPointTarget(hSpell, DOTA_UNIT_TARGET_TEAM_ENEMY)
            if vTargetLoc ~= nil then
                return {ability = hSpell, type = "point_target", target = vTargetLoc}
            end
        else
            local hTarget = BotAI:GetBestHeroTargetInRange(hSpell)
            if hTarget ~= nil and hTarget:IsNull() == false then
                return {ability = hSpell, type = "unit_target", target = hTarget}
            else
                return {ability = hSpell, type = "unit_target", target = hSpell:GetCaster()}
            end
        end
    end


        return nil
end

function BotAI:FindTreeTarget(hSpell)
    local Trees = GridNav:GetAllTreesAroundPoint(hSpell:GetCaster():GetAbsOrigin(), BotAI:GetSpellRange(hSpell), false)
    if #Trees == 0 then
        return nil
    end

    local nearestTree = nil
    local nearestLength = nil

    for i, v in pairs(Trees) do
        if(v ~= nil and v:IsNull() == false) then
            local len = (hSpell:GetCaster():GetAbsOrigin() - v:GetAbsOrigin()):Length2D()

            if (nearestLength == nil or len < nearestLength) then
                nearestLength = len
                nearestTree = v
            end
        end
    end

    return nearestTree
end

function BotAI:GetBestDirectionalPointTarget(hSpell, targetTeamType)
    if(hSpell == nil) then
        return nil
    end

    local nUnitsRequired = 1
    if hSpell:GetAbilityType() == ABILITY_TYPE_ULTIMATE then
        nUnitsRequired = 2
    end

    local nAbilityRadius = hSpell:GetAOERadius()
    if nAbilityRadius == 0 then
        nAbilityRadius = 250
    end


    local vLocation = GetTargetLinearLocation(hSpell:GetCaster():GetTeamNumber(),
            DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_CREEP+DOTA_UNIT_TARGET_BASIC,
            targetTeamType,
            hSpell:GetCaster():GetAbsOrigin(),
            BotAI:GetSpellRange(hSpell),
            nAbilityRadius,
            nUnitsRequired)

    if vLocation == vec3_invalid  then
        return nil
    end

    return vLocation
end


function BotAI:GetBestAOEPointTarget(hSpell, targetTeamType)
    if(hSpell == nil) then
        return nil
    end

    local nUnitsRequired = 1
    if hSpell:GetAbilityType() == ABILITY_TYPE_ULTIMATE then
        nUnitsRequired = 2
    end

    local nAbilityRadius = hSpell:GetAOERadius()
    if nAbilityRadius == 0 then
        nAbilityRadius = BotAI:GetSpellRange(hSpell)
    end

    if nAbilityRadius == 0 then
        nAbilityRadius = 250
    end

    if(hSpell:GetName() == "puck_illusory_orb") then
        nAbilityRadius = 400
    end

    if(nAbilityRadius > 1000) then
        nAbilityRadius = 1000
    end

    local vLocation = GetTargetAOELocation(hSpell:GetCaster():GetTeamNumber(),
            DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_CREEP,
            targetTeamType,
            hSpell:GetCaster():GetAbsOrigin(),
            BotAI:GetSpellRange(hSpell),
            nAbilityRadius,
            nUnitsRequired)
    if vLocation == vec3_invalid  then
        return nil
    end

    return vLocation
end

function BotAI:GetSpellRange(hSpell)
    if(hSpell == nil) then
        return 250
    end

    local baseCastRange = hSpell:GetCastRange(vec3_invalid, nil)
    if(baseCastRange == nil or baseCastRange < 250) then
        baseCastRange = 250
    end

    local caster = hSpell:GetCaster()
    if(caster == nil or caster:IsNull()) then
        return baseCastRange
    end
    
    return baseCastRange + 100
end

function BotAI:GetBestHeroTargetInRange(hSpell)
    local abilityName = hSpell:GetName()



    local hero = hSpell:GetCaster()
    local teamId = hero:GetTeamNumber()
    local radius = BotAI:GetSpellRange(hSpell)

    local enemies = FindUnitsInRadius(teamId, hero:GetAbsOrigin(), hero, radius, DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_HERO, UNIT_FILTER, FIND_CLOSEST, true)
    --print("enemies..",#enemies)
    if #enemies == 0 then
        return nil
    end

    local firstEnemy = nil
    local rangedEnemy = nil

    for index = 1, #enemies do
        if(enemies[index]:IsAlive() and enemies[index]:IsInvulnerable() == false) then
            if(enemies[index]:IsMagicImmune()==false and (enemies[index]:IsInvisible() == false)) then
                rangedEnemy = enemies[index]
                break
            end
        end
    end
    --print("rangedEnemy..",rangedEnemy)
    return rangedEnemy
end

function BotAI:GetBestCreepTarget(hSpell)
    local enemies = FindUnitsInRadius(hSpell:GetCaster():GetTeamNumber(), hSpell:GetCaster():GetAbsOrigin(), hSpell:GetCaster(),
            BotAI:GetSpellRange(hSpell), DOTA_UNIT_TARGET_TEAM_ENEMY, DOTA_UNIT_TARGET_CREEP+DOTA_UNIT_TARGET_BASIC, UNIT_FILTER, FIND_CLOSEST, true)

    if #enemies == 0 then
        return nil
    end

    for _, v in pairs(enemies) do
        if( v:IsAlive()) then
            return v
        end
    end

    return nil
end

function BotAI:IsNoTargetSpellCastValid(hSpell, targetTeamType)
    local nUnitsRequired = 1
    if hSpell:GetAbilityType() == ABILITY_TYPE_ULTIMATE then
        nUnitsRequired = 2
    end

    local nAbilityRadius = hSpell:GetAOERadius()
    if nAbilityRadius == 0 then
        nAbilityRadius = 600
    end


    local units = FindUnitsInRadius(hSpell:GetCaster():GetTeamNumber(), hSpell:GetCaster():GetAbsOrigin(),
            hSpell:GetCaster(), nAbilityRadius, targetTeamType, DOTA_UNIT_TARGET_HERO+DOTA_UNIT_TARGET_BASIC, UNIT_FILTER, 0, true)

    if #units < nUnitsRequired then
        return false
    end

    return true
end

function BotAI:CastSpell(spellData)
    if BotAI.BOT_AI_DEBUG ==true then
        print("[BotAI:CastSpell]    ",spellData.ability:GetAbilityName(),",target=",spellData.target)
    end
    local hSpell = spellData.ability

    if hSpell == nil then
        return 0.1
    end

    local caster = hSpell:GetCaster()
    if(caster == nil or caster:IsNull() or caster:IsAlive() == false) then
        return 0.1
    end


    if(spellData.type == "unit_target") then
        if BotAI.BOT_AI_DEBUG ==true then
            print("CastSpell..type  DOTA_ABILITY_BEHAVIOR_UNIT_TARGET ")
        end
        return BotAI:CastSpellUnitTarget(hSpell, spellData.target)
    end

    if(spellData.type == "point_target") then
        if BotAI.BOT_AI_DEBUG ==true then
            print("CastSpell..type  DOTA_ABILITY_BEHAVIOR_POINT ")
        end
        return BotAI:CastSpellPointTarget(hSpell, spellData.target)
    end

    if(spellData.type == "no_target") then
        if BotAI.BOT_AI_DEBUG ==true then
            print("CastSpell..type  DOTA_ABILITY_BEHAVIOR_NO_TARGET ")
        end
        return BotAI:CastSpellNoTarget(hSpell)
    end

    if(spellData.type == "tree_target") then
        return BotAI:CastSpellTreeTarget(hSpell, spellData.target)
    end

    return 0.1
end

function BotAI:CastSpellNoTarget(hSpell)
    local caster = hSpell:GetCaster()
    if(caster == nil or caster:IsNull() or caster:IsAlive() == false) then
        return 0.1
    end

    caster:CastAbilityNoTarget(hSpell, caster:GetPlayerID())

    return BotAI:GetSpellCastTime(hSpell)
end

function BotAI:CastSpellUnitTarget(hSpell, hTarget)
    local caster = hSpell:GetCaster()
    if(caster == nil or caster:IsNull() or caster:IsAlive() == false) then
        return 0.1
    end

    if(hTarget == nil or hTarget:IsNull() or hTarget:IsAlive()== false) then
        return 0.1
    end

    if(GridNav:CanFindPath(caster:GetAbsOrigin(), hTarget:GetAbsOrigin()) == false) then
        return 0.1
    end

    caster:CastAbilityOnTarget(hTarget, hSpell, caster:GetPlayerID())

    return BotAI:GetSpellCastTime(hSpell)
end

function BotAI:CastSpellTreeTarget(hSpell, treeTarget)
    local caster = hSpell:GetCaster()
    if(caster == nil or caster:IsNull() or caster:IsAlive() == false) then
        return 0.1
    end

    if(treeTarget == nil or treeTarget:IsNull()) then
        return 0.1
    end

    caster:CastAbilityOnTarget(treeTarget, hSpell, caster:GetPlayerID())

    return BotAI:GetSpellCastTime(hSpell)
end

function BotAI:CastSpellPointTarget(hSpell, vLocation)
    local caster = hSpell:GetCaster()
    if(caster == nil or caster:IsNull() or caster:IsAlive() == false) then
        return 0.1
    end

    if(vLocation == nil) then
        return 0.1
    end

    caster:CastAbilityOnPosition(vLocation, hSpell, caster:GetPlayerID())

    return BotAI:GetSpellCastTime(hSpell)
end

function BotAI:GetSpellCastTime(hSpell)
    if(hSpell ~= nil and hSpell:IsNull() == false) then
        local flCastPoint = math.max(0.25, hSpell:GetCastPoint() + hSpell:GetBackswingTime())
        if(flCastPoint < 0.2) then
            flCastPoint = 0.2
        end

        return flCastPoint
    end
    return 0.2
end